/*
 * Copyright (c) 2010, Nikolaus Moll
 * All rights reserved.
 */
package net.miblounge.gradecalculator.srdxeditor.models;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import net.miblounge.gradecalculator.core.descriptors.StudyRegulationsDescription;
import net.miblounge.gradecalculator.core.descriptors.courses.Course;
import net.miblounge.gradecalculator.core.descriptors.modulelistitems.IndividualModule;
import net.miblounge.gradecalculator.core.descriptors.modulelistitems.Module;
import net.miblounge.gradecalculator.core.descriptors.modulelistitems.SelectableModuleLists;
import net.miblounge.gradecalculator.core.descriptors.modulelists.ModuleList;
import net.miblounge.gradecalculator.core.helper.Helper;
import net.miblounge.gradecalculator.core.interfaces.IModuleIdentificator;
import net.miblounge.gradecalculator.core.interfaces.IModuleListItem;
import net.miblounge.gradecalculator.guicommon.interfaces.IMultiColumnTreeModelListener;
import net.miblounge.gradecalculator.guicommon.models.AbstractMultiColumnTreeModel;
import net.miblounge.gradecalculator.guicommon.widgets.TreeIndex;

import com.trolltech.qt.core.Qt;
import com.trolltech.qt.gui.QBrush;
import com.trolltech.qt.gui.QColor;
import com.trolltech.qt.gui.QFont;
import com.trolltech.qt.gui.QWidget;

public class RegulationTreeModel extends AbstractMultiColumnTreeModel implements IMultiColumnTreeModelListener {
	private static final int COLNAME = 0;
	private static final int COLECTS = 1;
	private static final int COLHOURSPERWEEK = 2;
	private static final int COLTYPE = 3;
	private static final int COLGRADED = 4;
	private static final int COLUNGRADED = 5;
	private static final int COLSEMESTER = 6;
	private static final int COLEXAMNUMBER = 7;
	private static final int COLCOURSENUMBER = 8;

	private static final int ALRIGHT = Qt.AlignmentFlag.createQFlags(Qt.AlignmentFlag.AlignRight, 
			Qt.AlignmentFlag.AlignVCenter).value();
	private static final int ALLEFT = Qt.AlignmentFlag.createQFlags(Qt.AlignmentFlag.AlignLeft, 
			Qt.AlignmentFlag.AlignVCenter).value();
	private static final int ALCENTER = Qt.AlignmentFlag.createQFlags(Qt.AlignmentFlag.AlignCenter, 
			Qt.AlignmentFlag.AlignVCenter).value();
	
	private final StudyRegulationsDescription regulations;

	private final QFont fontModule;
	private final QFont fontCourse;
	

	public RegulationTreeModel(final QWidget parent, final StudyRegulationsDescription regulations) {
		this.regulations = regulations;
        this.addTreeEventListener(this);

		registerMimeClass(Course.class);
		registerMimeClass(Module.class);
		registerMimeClass(IndividualModule.class);
		registerMimeClass(ModuleList.class);
		registerMimeClass(SelectableModuleLists.class);
		
		fontModule = new QFont(parent.font().family(), parent.font().pointSize(),
				QFont.Weight.Bold.value(), false);
		fontCourse = new QFont(parent.font().family(), parent.font().pointSize(),
				QFont.Weight.Normal.value(), false);
	}
	

	private int getModuleCount() {
		return regulations.getModuleList().getModules().size();
	}

	public int getColumnCount() {
		return 9;
	}

	public QFont getFont(final Object data) {
		if (data instanceof Course) {
			return fontCourse;
		}
		
		return fontModule;
	}
	
	public QBrush getBrush(final Object data) {
		return new QBrush(QColor.black);
	}
		
	public int getAlignment(final int column)
	{
		switch (column) {
		case COLECTS:
			return ALRIGHT;
			
		case COLHOURSPERWEEK:
			return ALRIGHT;

		case COLTYPE:
			return ALCENTER;

		case COLGRADED:
			return ALCENTER;

		case COLUNGRADED:
			return ALCENTER;
			
		default:
			return ALLEFT;
			
		}
	}
	
	public int getRowCount(final TreeIndex index) {
		if (index == null) {
			// root
			return 2 + getModuleCount();
		}
		
		final Object data = getData(index);
		
		if (data instanceof Module) {
			final Module module = (Module)data;
			return module.getCourses().size();
		}
		if (data instanceof SelectableModuleLists) {
			final SelectableModuleLists selectableModuleLists = (SelectableModuleLists)data;
			return selectableModuleLists.getModuleLists().size();
		}
		if (data instanceof ModuleList) {
			final ModuleList moduleList = (ModuleList)data;
			return moduleList.getModules().size();
		}
		
		return 0;
	}
	
	public List<String> getHeaderLabels()
	{
		final List<String> result = new ArrayList<String>(getColumnCount());
		for (int i = 0; i < getColumnCount(); i++) {
			result.add(getHeaderText(i));
		}
		return result;
	}
	
	public String getHeaderText(final int column)
	{
		switch (column) {
		case COLNAME:
			return "Name";
			
		case COLECTS:
			return "ECTS";

		case COLHOURSPERWEEK:
			return "hpw";

		case COLTYPE:
			return "Type";
		
		case COLGRADED:
			return "Graded";

		case COLUNGRADED:
			return "Practical";

		case COLSEMESTER:
			return "Semester";
			
		case COLEXAMNUMBER:
			return "Exam-No";

		case COLCOURSENUMBER:
			return "Course-No";
			
		default:
			return "";
		}
	}
	
	public String getText(final Object data, final int column)
	{
		if (data instanceof Course) {
			return getCourseText((Course)data, column);
		}
		if (data instanceof Module) {
			return getModuleText((Module)data, column);
		}
		if (data instanceof SelectableModuleLists) {
			return getSelectableModuleListsText((SelectableModuleLists)data, column);
		}
		if (data instanceof ModuleList) {
			return getModuleListText((ModuleList)data, column);
		}
		
		return getOverallText(column);
	}

	private String getCourseText(final Course course, final int column)
	{
		switch (column) {
		case COLNAME:
			return course.getName();
			
		case COLECTS:
			return Helper.formatECTS(course.getECTS());
			
		case COLHOURSPERWEEK:
			return String.valueOf(course.getHoursPerWeek());

		case COLTYPE:
			return course.getType().toString();
		
		case COLGRADED:
			if (course.getGradedPerformance().isEmpty()) {
				return "";
			}
			return course.getGradedPerformance().toString();

		case COLUNGRADED:
			if (course.getUngradedPerformance().isEmpty()) {
				return "";
			}
			return course.getUngradedPerformance().toString();

		case COLSEMESTER:
			return String.valueOf(course.getSemester());
			
		case COLEXAMNUMBER:
			return String.valueOf(course.getExamNumber());

		case COLCOURSENUMBER:
			return String.valueOf(course.getCourseNumber());
			
			
		default:
			return "";
		}
	}
	
	private String getModuleText(final Module module, final int column)
	{
		switch (column) {
		case COLNAME:
			return module.getName();
			
		case COLECTS:
			return Helper.formatECTS(module.getECTS());

		case COLHOURSPERWEEK:
			return String.valueOf(module.getHoursPerWeek());

		case COLTYPE:
			if (module instanceof IndividualModule) {
				return "IM";
			}
			return "";
			
		default:
			return "";
		}
	}
	
	private String getSelectableModuleListsText(final SelectableModuleLists selectableModuleLists, final int column)
	{
		switch (column) {
		case COLNAME:
			return selectableModuleLists.getName();
			
		case COLECTS:
			return Helper.formatECTS(selectableModuleLists.getECTS());
			
		case COLHOURSPERWEEK:
			return String.valueOf(selectableModuleLists.getHoursPerWeek());

		case COLTYPE:
			return "SML";	// TODO remove / change
			
		default:
			return "";
		}
	}
	
	private String getModuleListText(final ModuleList moduleList, final int column)
	{
		switch (column) {
		case COLNAME:
			return moduleList.getName();
			
		case COLECTS:
			return Helper.formatECTS(moduleList.getECTS());
			
		case COLHOURSPERWEEK:
			return String.valueOf(moduleList.getHoursPerWeek());

		case COLTYPE:
			return "ML";	// TODO remove / change
			
		default:
			return "";
		}
	}
	
	private String getOverallText(final int column)
	{
		switch (column) {
		case COLNAME:
			return "Overall";
			
		case COLECTS:
			return Helper.formatECTS(regulations.getECTS());

		case COLHOURSPERWEEK:
			return String.valueOf(regulations.getHoursPerWeek());
			
		default:
			return "";
		}
	}
	
	public boolean isInlineEditorAllowed(final TreeIndex index, final int column)
	{
		return false;
//		if (column < COLGRADES) {
//			return false;
//		}
//		
//		final Object data = getData(index);
//		
//		if (!(data instanceof Course)) {
//			return false;
//		}
//		
//		final Course course = (Course)data;
//		
//		if (course.getGradedPerformance() == ExamType.None) {
//			// no graded performance => no setGrade
//			return false;
//		}
//
//		final ExaminationResult result = regulations.getStudent().getResult(course);
//		final boolean hasPassedGrade = !result.getGrade(ExaminationResultType.PASSED).equals("");
//		final boolean isPassedGrade = (column == COLGRADES);
//		
//		if ((!isPassedGrade) && (hasPassedGrade)) {
//			// no estimation allowed
//			return false;
//		}		
//		
//		return true;
	}

	private boolean isModuleRow(final int row)
	{
		final int moduleRow = row - 1;
		if ((moduleRow < 0) || (moduleRow >= getModuleCount())) {
			return false;
		}
		
		return true;
	}
	
	@Override
	protected <T> Object getChild(final T item, final int index)
	{
		if (item == null) {
			// root
			if (!isModuleRow(index)) {
				return regulations;
			}
			return regulations.getModuleList().getModules().get(index - 1);
		}
		
		if (item instanceof ModuleList) {
			return ((ModuleList)item).getModules().get(index);
		}

		if (item instanceof SelectableModuleLists) {
			return ((SelectableModuleLists)item).getModuleLists().get(index);
		}

		if (item instanceof Module) {
			return ((Module)item).getCourses().get(index);
		}
		
		throw new IllegalStateException("Cannot determine child of class " + item.getClass());
	}

	public void setText(final TreeIndex index, final int column, final String text) {
//		Object data = getData(index);
//		if (data instanceof Course) {
//			setGrade(index, column, text, (Course)data);
//		}
	}

	public void addModuleListItem(final TreeIndex parent, final IModuleListItem module) {
		final ModuleList moduleList;
		final int overallRowDelta;
		if (parent != null) {
			final Object parentObject = getData(parent);
			if (!(parentObject instanceof ModuleList)) {
				throw new IllegalStateException("Illegal parent (" + parentObject + ")");
			}
			
			moduleList = (ModuleList)parentObject;
			overallRowDelta = 0;
		} else {
			overallRowDelta = 1;
			moduleList = regulations.getModuleList();
		}
		
		moduleList.addModule(module);
		final int moduleIndex = moduleList.getModules().indexOf(module) + overallRowDelta;
		
		notifyChildInserted(parent, moduleIndex);
		notifySelectItem(new TreeIndex(moduleIndex, parent));
	}

	public void addCourse(final TreeIndex parent, final Course course) {
		final Object parentObject = getData(parent);
		if (!(parentObject instanceof Module)) {
			throw new IllegalStateException("Illegal parent (" + parentObject + ")");
		}
		
		final Module module = (Module)parentObject;
		
		module.addCourse(course);
		final int moduleIndex = module.getCourses().indexOf(course);
		
		notifyChildInserted(parent, moduleIndex);
		notifySelectItem(new TreeIndex(moduleIndex, parent));
	}

	public void addModuleList(final TreeIndex parent, final ModuleList moduleList) {
		final Object parentObject = getData(parent);
		if (!(parentObject instanceof SelectableModuleLists)) {
			throw new IllegalStateException("Illegal parent (" + parentObject + ")");
		}
		
		final SelectableModuleLists selectableModuleLists = (SelectableModuleLists)parentObject;
		
		selectableModuleLists.addModuleList(moduleList);
		final int moduleIndex = selectableModuleLists.getModuleLists().indexOf(moduleList);
		
		notifyChildInserted(parent, moduleIndex);
		notifySelectItem(new TreeIndex(moduleIndex, parent));
	}

	public void removeCourse(final TreeIndex index, final Course course) {
		final TreeIndex parentIndex = index.getParent();
		final Object parentData = getData(parentIndex);
		if (!(parentData instanceof Module)) {
			throw new IllegalStateException("Module expected as parent, actual parent is " + parentData.getClass());
		}
		
		final Module module = (Module)parentData;
		final int courseRow = module.getCourses().indexOf(course);
		
		module.removeCourse(course);
		notifyChildRemoved(parentIndex, courseRow);
	}

	public void removeModuleListItem(final TreeIndex index, final IModuleListItem moduleListItem) {
		final TreeIndex parentIndex = index.getParent();
		final ModuleList moduleList;
		if (parentIndex == null) {
			moduleList = regulations.getModuleList();
		} else {
			final Object parentData = getData(parentIndex);
			if (!(parentData instanceof ModuleList)) {
				throw new IllegalStateException("ModuleList expected as parent, actual parent is " + parentData.getClass());
			}
		
			moduleList = (ModuleList)parentData;
		}
		
		final int moduleListItemRow = moduleList.getModules().indexOf(moduleListItem);
		
		moduleList.removeModule(moduleListItem);
		notifyChildRemoved(parentIndex, moduleListItemRow + rowDelta(parentIndex));
	}

	public void removeModuleList(final TreeIndex index, final ModuleList moduleList) {
		final TreeIndex parentIndex = index.getParent();
		final Object parentData = getData(parentIndex);
		if (!(parentData instanceof SelectableModuleLists)) {
			throw new IllegalStateException("SelectableModuleLists expected as parent, actual parent is " + parentData.getClass());
		}
		
		final SelectableModuleLists selectableModuleLists = (SelectableModuleLists)parentData;
		final int row = selectableModuleLists.getModuleLists().indexOf(moduleList);
		
		selectableModuleLists.removeModuleList(moduleList);
		notifyChildRemoved(parentIndex, row + rowDelta(parentIndex));
	}

	
	@Override
	public Object getRootData()
	{
		return regulations.getModuleList();
	}
	
	public TreeIndex getIndexOf(final Object data)
	{
		if (data == null) {
			return null;
		}
		if (data instanceof StudyRegulationsDescription) {
			return null;
		}
		if (!(data instanceof IModuleIdentificator)) {
			throw new IllegalStateException("Wrong class, expected IModuleIdentificator, is " + data.getClass());
		}
		
		final Stack<Integer> stack = new Stack<Integer>();
		IModuleIdentificator current = (IModuleIdentificator)data;
		
		while (current != null) {
			final IModuleIdentificator parent = current.getParent();
			if (parent != null) {
				if (parent instanceof Module) {
					stack.push(((Module)parent).getCourses().indexOf(current));
				}
				if (parent instanceof ModuleList) {
					stack.push(((ModuleList)parent).getModules().indexOf(current));
				}
				if (parent instanceof SelectableModuleLists) {
					stack.push(((SelectableModuleLists)parent).getModuleLists().indexOf(current));
				}
			}
			
			current = parent;
		}
		
		int delta = 1;
		TreeIndex result = null;
		while (!stack.isEmpty()) {
			result = new TreeIndex(stack.pop() + delta, result);
			delta = 0;
		}
		
		return result;
	}

	// DRAG & DROP
	public boolean isDragEnabled()
	{
		return true;
	}

	public boolean isDropEnabled()
	{
		return true;
	}
	
	public boolean isDropIndicatorShown()
	{
		return true;
	}	
	
	public boolean isDragAllowed(final TreeIndex index, final Object data) {
		if (data instanceof StudyRegulationsDescription) {
			return false;
		}
		
		return true;
	}
	
	private boolean isCourseDropAllowed(final Object target)
	{
		if (target instanceof IndividualModule) {
			return false;
		}
		if (target instanceof Module) {
			return true;
		}
		if (target instanceof Course) {
			return true;
		}
		
		return false;
	}
	
	private boolean isModuleDropAllowed(final Object target)
	{
		if (target instanceof ModuleList) {
			return true;
		}
		if (target instanceof IModuleListItem) {
			return true;
		}
		
		return false;
	}
	
	private boolean isModuleListDropAllowed(final Object target)
	{
		if (target instanceof ModuleList) {
			return true;
		}
		if (target instanceof SelectableModuleLists) {
			return true;
		}
		
		return false;
	}
	
	private boolean isSMLDropAllowed(final SelectableModuleLists source, final Object target)
	{
		if ((target instanceof ModuleList) || (target instanceof SelectableModuleLists)) {
			// check if target is child of item itself
			IModuleIdentificator item = (IModuleIdentificator)target;
			while (item != null) {
				if (source == item) {
					return false;
				}
				item = item.getParent();
			}
			
			return true;
		}
		if (target instanceof IModuleListItem) {
			return true;
		}
		
		return false;
	}
	
	public boolean isDropAllowed(final Object draggingObject, final TreeIndex index,
			final Object target) {
		if (draggingObject == target) {
			return false;
		}

		if (draggingObject instanceof Course) {
			return isCourseDropAllowed(target);
		}

		if (draggingObject instanceof SelectableModuleLists) {
			return isSMLDropAllowed((SelectableModuleLists)draggingObject, target);
		}

		if (draggingObject instanceof IModuleListItem) {
			return isModuleDropAllowed(target);
		}

		if (draggingObject instanceof ModuleList) {
			return isModuleListDropAllowed(target);
		}
		
		return false;
	}

	public void dropItem(final TreeIndex sourceIndex, final Object draggingObject, final TreeIndex targetIndex, final Object target) {
		if (draggingObject instanceof Course) {
			dropCourse(sourceIndex, (Course)draggingObject, targetIndex, target);
		}
		if (draggingObject instanceof IModuleListItem) {
			dropModule(sourceIndex, (IModuleListItem)draggingObject, targetIndex, target);
		}
		if (draggingObject instanceof ModuleList) {
			dropModuleList(sourceIndex, (ModuleList)draggingObject, targetIndex, target);
		}
	}

	private void dropCourse(final TreeIndex sourceIndex, final Course course,
			final TreeIndex targetIndex, final Object target) {
		if (target instanceof Course) {
			final Course moveBefore = (Course)target;
			
			final TreeIndex sourceParent = sourceIndex.getParent();
			final Module sourceModule = (Module)getData(sourceParent);

			final TreeIndex targetParent = targetIndex.getParent();
			final Module targetModule = (Module)getData(targetParent);

			final int sourceRow = sourceModule.getCourses().indexOf(course);
			sourceModule.removeCourse(course);

			targetModule.insertCourse(course, moveBefore);

			final TreeIndex oldChildIndex = new TreeIndex(sourceRow, sourceParent);
			final TreeIndex newChildIndex = new TreeIndex(targetModule.getCourses().indexOf(course), targetParent);
			notifyChildMoved(oldChildIndex, newChildIndex); 
		}
		
		if (target instanceof Module) {
			final Module module = (Module)target;

			final TreeIndex sourceParent = sourceIndex.getParent();
			final Module sourceModule = (Module)getData(sourceParent);


			final int sourceRow = sourceModule.getCourses().indexOf(course);
			sourceModule.removeCourse(course);

			module.addCourse(course);

			final TreeIndex oldChildIndex = new TreeIndex(sourceRow, sourceParent);
			final TreeIndex newChildIndex = new TreeIndex(module.getCourses().indexOf(course), targetIndex);
			notifyChildMoved(oldChildIndex, newChildIndex); 
		}
	}
	

	private int rowDelta(final TreeIndex parent)
	{
		if (parent == null) {
			return 1;
		}
		
		return 0;
	}

	private void dropModule(final TreeIndex sourceIndex, final IModuleListItem module,
			final TreeIndex targetIndex, final Object target) {
		if (target instanceof IModuleListItem) {
			final IModuleListItem moveBefore = (IModuleListItem)target;

			final TreeIndex sourceParent = sourceIndex.getParent();
			final ModuleList sourceModuleList = (ModuleList)getData(sourceParent);

			final TreeIndex targetParent = targetIndex.getParent();
			final ModuleList targetModuleList = (ModuleList)getData(targetParent);
			
			final int sourceRow = sourceModuleList.getModules().indexOf(module) + rowDelta(sourceParent);
			sourceModuleList.removeModule(module);
			
			targetModuleList.insertModule(module, moveBefore);
			
			final TreeIndex oldChildIndex = new TreeIndex(sourceRow, sourceParent);
			final TreeIndex newChildIndex = getIndexOf(module);
			notifyChildMoved(oldChildIndex, newChildIndex); 
		}
		
		if (target instanceof ModuleList) {
			final ModuleList moduleList = (ModuleList)target;

			final TreeIndex sourceParent = sourceIndex.getParent();
			final ModuleList sourceModuleList = (ModuleList)getData(sourceParent);

			final int sourceRow = sourceModuleList.getModules().indexOf(module) + rowDelta(sourceParent);
			sourceModuleList.removeModule(module);

			moduleList.addModule(module);

			final TreeIndex newTargetIndex = getIndexOf(module);

			final TreeIndex oldChildIndex = new TreeIndex(sourceRow, sourceParent);
			final TreeIndex newChildIndex = newTargetIndex;
			notifyChildMoved(oldChildIndex, newChildIndex); 
		}
	}

	private void dropModuleList(final TreeIndex sourceIndex, final ModuleList moduleList,
			final TreeIndex targetIndex, final Object target) {
		if (target instanceof ModuleList) {
			final ModuleList moveBefore = (ModuleList)target;

			final TreeIndex sourceParent = sourceIndex.getParent();
			final SelectableModuleLists sourceSelectableModuleLists = (SelectableModuleLists)getData(sourceParent);

			final TreeIndex targetParent = targetIndex.getParent();
			final SelectableModuleLists targetSelectableModuleLists = (SelectableModuleLists)getData(targetParent);
			
			final int sourceRow = sourceSelectableModuleLists.getModuleLists().indexOf(moduleList);
			sourceSelectableModuleLists.removeModuleList(moduleList);

			targetSelectableModuleLists.insertModuleList(moduleList, moveBefore);
			final int targetRow = targetSelectableModuleLists.getModuleLists().indexOf(moduleList) + rowDelta(targetParent);

			final TreeIndex oldChildIndex = new TreeIndex(sourceRow, sourceParent);
			final TreeIndex newChildIndex = new TreeIndex(targetRow, targetParent);
			notifyChildMoved(oldChildIndex, newChildIndex); 
		}
		
		if (target instanceof SelectableModuleLists) {
			final SelectableModuleLists selectableModuleList = (SelectableModuleLists)target;

			final TreeIndex sourceParent = sourceIndex.getParent();
			final SelectableModuleLists sourceSelectableModuleLists = (SelectableModuleLists)getData(sourceParent);

			final int sourceRow = sourceSelectableModuleLists.getModuleLists().indexOf(moduleList);
			sourceSelectableModuleLists.removeModuleList(moduleList);

			selectableModuleList.addModuleList(moduleList);

			final TreeIndex oldChildIndex = new TreeIndex(sourceRow, sourceParent);
			final TreeIndex newChildIndex = new TreeIndex(selectableModuleList.getModuleLists().indexOf(moduleList), targetIndex);
			notifyChildMoved(oldChildIndex, newChildIndex); 
		}
	}
	
	public boolean isRecursiveUpdateAllowed(final TreeIndex row)
	{
		if (row.getParent() != null) {
			return true;
		}
		
		return isModuleRow(row.getRow()); 
	}

	@Override
	public void dataChangedEvent(final TreeIndex row) {
		if (row == null) {
			notifyDataChanged(new TreeIndex(0));
			notifyDataChanged(new TreeIndex(getModuleCount() + 1));
		}
	}

	@Override
	public void dataChangedEvent(final TreeIndex row, final int column, final String newText) {
	}

	@Override
	public void childInserted(final TreeIndex parent, final int row) {
	}

	@Override
	public void childRemoved(final TreeIndex parent, final int row) {
	}

	@Override
	public void childMoved(final TreeIndex sourceIndex, final TreeIndex targetIndex) {
	}

	@Override
	public void selectChild(final TreeIndex index) {
	}
	
}

